﻿#include "export_console.hh"
#include "../../../box/service_box.hh"
#include "../../../console/console_impl.hh"
#include "../lua_util.hh"

kratos::lua::LuaConsole::LuaConsole(kratos::service::ServiceBox *box,
                                    lua_State *L, LuaServiceImpl *service) {
  box_ = box;
  L_ = L;
  service_ = service;
}

kratos::lua::LuaConsole::~LuaConsole() {}

auto kratos::lua::LuaConsole::do_register() -> bool {
  set_class(L_, this, "_box_console");
  lua_reg_key_ = LuaUtil::new_registry_table(L_);
  LuaUtil::register_function(L_, "kratos_add_switch",
                             &LuaConsole::lua_add_switch);
  LuaUtil::register_function(L_, "kratos_add_selection",
                             &LuaConsole::lua_add_selection);
  box_->write_log_line(klogger::Logger::VERBOSE,
                       "[lua][console]Console module installed");
  return true;
}

auto kratos::lua::LuaConsole::update(std::time_t ms) -> void {}

auto kratos::lua::LuaConsole::do_cleanup() -> void {
  auto switch_cb_temp = switch_cb_info_map_;
  for (auto &[k, v] : switch_cb_temp) {
    remove_switch_callback(v);
  }
  auto selection_cb_temp = selection_cb_info_map_;
  for (auto &[k, v] : selection_cb_temp) {
    remove_selection_callback(v);
  }
  switch_cb_info_map_.clear();
  selection_cb_temp.clear();
  LuaUtil::remove_registry_key(L_, lua_reg_key_);
}

auto kratos::lua::LuaConsole::new_index() -> int { return index_++; }

auto kratos::lua::LuaConsole::add_switch_callback(const SwtichCallback &cb)
    -> int {
  auto index = new_index();
  for (const auto &[k, v] : switch_cb_info_map_) {
    if (v.name == cb.name) {
      remove_switch_callback(v);
      break;
    }
  }
  switch_cb_info_map_[index] = cb;
  return index;
}

auto kratos::lua::LuaConsole::remove_switch_callback(const SwtichCallback &cb)
    -> void {
  LuaUtil::LuaState lua_state(L_);
  lua_state.remove_lua_reg_function(lua_reg_key_, cb.change_key);
  lua_state.remove_lua_reg_function(lua_reg_key_, cb.refresh_key);
  for (auto it = switch_cb_info_map_.begin(); it != switch_cb_info_map_.end();
       it++) {
    if (it->second.name == cb.name) {
      switch_cb_info_map_.erase(it);
      return;
    }
  }
}

auto kratos::lua::LuaConsole::add_selection_callback(
    const SelectionCallback &cb) -> int {
  auto index = new_index();
  for (const auto &[k, v] : selection_cb_info_map_) {
    if (v.name == cb.name) {
      remove_selection_callback(v);
      break;
    }
  }
  selection_cb_info_map_[index] = cb;
  return index;
}

auto kratos::lua::LuaConsole::remove_selection_callback(
    const SelectionCallback &cb) -> void {
  LuaUtil::LuaState lua_state(L_);
  lua_state.remove_lua_reg_function(lua_reg_key_, cb.change_key);
  lua_state.remove_lua_reg_function(lua_reg_key_, cb.refresh_key);
  for (auto it = selection_cb_info_map_.begin();
       it != selection_cb_info_map_.end(); it++) {
    if (it->second.name == cb.name) {
      selection_cb_info_map_.erase(it);
      return;
    }
  }
}

void kratos::lua::LuaConsole::switch_refresh_cb(
    kratos::console::Console &console, kratos::console::ConsoleSwitch &cs) {
  LuaUtil::LuaState lua_state(L_);
  auto index = (int)cs.get_user_data();
  auto it = switch_cb_info_map_.find(index);
  if (it == switch_cb_info_map_.end()) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Switch refresh callback not found");
    return;
  }
  // 调用回调
  lua_state.get_lua_reg_function(lua_reg_key_, it->second.refresh_key);
  std::string error_string;
  if (!LuaUtil::resume(L_, error_string)) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Switch refresh callback error:" +
                             error_string);
    lua_state.restore_stack();
    return;
  }
  auto status = LuaUtil::get_status(L_);
  if (status == ThreadState::YIELD) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Switch refresh callback yield");
    lua_state.restore_stack();
    return;
  }
  // display_name, on_off, tips
  if (!lua_state.isstring(-3) || !lua_state.isbool(-2) ||
      !lua_state.isstring(-1)) {
    box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]Switch refresh callback return value type error");
    lua_state.restore_stack();
    return;
  }
  const auto *display_name = lua_state.get<const char *>(-3);
  bool on_off = lua_state.get<bool>(-2);
  const auto *tips = lua_state.get<const char *>(-1);
  cs.set_display_name(display_name);
  cs.set_on_off(on_off);
  cs.set_tips(tips);
  lua_state.restore_stack();
}

bool kratos::lua::LuaConsole::switch_change_cb(
    kratos::console::Console &console, bool on_off, std::string &result) {
  LuaUtil::LuaState lua_state(L_);
  auto index = (int)console.get_current_user_data();
  auto it = switch_cb_info_map_.find(index);
  if (it == switch_cb_info_map_.end()) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Switch change callback not found");
    return false;
  }
  // 调用回调
  lua_state.get_lua_reg_function(lua_reg_key_, it->second.change_key);
  lua_state.push(on_off);
  std::string error_string;
  if (!LuaUtil::resume_nargs(L_, error_string, 1)) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Switch change callback error:" +
                             error_string);
    lua_state.restore_stack();
    return false;
  }
  auto status = LuaUtil::get_status(L_);
  if (status == ThreadState::YIELD) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Switch change callback yield");
    lua_state.restore_stack();
    return false;
  }
  // 获取返回值, bool, result
  if (!lua_state.isbool(-2) || !lua_state.isstring(-1)) {
    box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]Switch change callback return value type error");
    lua_state.restore_stack();
    return false;
  }
  bool success = lua_state.get<bool>(-2);
  const auto *error = lua_state.get<const char *>(-1);
  if (error) {
    result = error;
  }
  return success;
}

void kratos::lua::LuaConsole::selection_refresh_cb(
    kratos::console::Console &console, kratos::console::ConsoleSelection &cs) {
  LuaUtil::LuaState lua_state(L_);
  auto index = (int)cs.get_user_data();
  auto it = selection_cb_info_map_.find(index);
  if (it == selection_cb_info_map_.end()) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Selection refresh callback not found");
    return;
  }
  const auto &info = it->second;
  // 调用回调
  lua_state.get_lua_reg_function(lua_reg_key_, info.refresh_key);
  std::string error_string;
  if (!LuaUtil::resume(L_, error_string)) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Selection refresh callback error:" +
                             error_string);
    lua_state.restore_stack();
    return;
  }
  auto status = LuaUtil::get_status(L_);
  if (status == ThreadState::YIELD) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Selection refresh callback yield");
    lua_state.restore_stack();
    return;
  }
  // name, tips, selection
  if (!lua_state.isstring(-3) || !lua_state.isstring(-2) ||
      !lua_state.istable(-1)) {
    box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]Selection refresh callback return value type error");
    lua_state.restore_stack();
    return;
  }
  auto selections = lua_state.get<std::vector<std::string>>(-1);
  const auto *name = lua_state.get<const char *>(-3);
  const auto *tips = lua_state.get<const char *>(-2);
  cs.set_display_name(name);
  cs.set_tips(tips);
  cs.add_selection(selections);
  lua_state.restore_stack();
}

bool kratos::lua::LuaConsole::selection_change_cb(
    kratos::console::Console &console, const std::string &name,
    std::string &result) {
  LuaUtil::LuaState lua_state(L_);
  auto index = (int)console.get_current_user_data();
  auto it = selection_cb_info_map_.find(index);
  if (it == selection_cb_info_map_.end()) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Selection change callback not found");
    return false;
  }
  // 调用回调
  lua_state.get_lua_reg_function(lua_reg_key_, it->second.change_key);
  lua_state.push(name);
  std::string error_string;
  if (!LuaUtil::resume_nargs(L_, error_string, 1)) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Selection change callback error:" +
                             error_string);
    lua_state.restore_stack();
    return false;
  }
  auto status = LuaUtil::get_status(L_);
  if (status == ThreadState::YIELD) {
    box_->write_log_line(klogger::Logger::FAILURE,
                         "[lua][console]Selection change callback yield");
    lua_state.restore_stack();
    return false;
  }
  // 获取返回值, bool, result
  if (!lua_state.isbool(-2) || !lua_state.isstring(-1)) {
    box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]Selection change callback return value type error");
    lua_state.restore_stack();
    return false;
  }
  bool success = lua_state.get<bool>(-2);
  const auto *error = lua_state.get<const char *>(-1);
  if (error) {
    result = error;
  }
  return success;
}

int kratos::lua::LuaConsole::lua_add_switch(lua_State *l) {
  LuaUtil::LuaState lua_state(l);
  auto *console = get_class<LuaConsole>(l, "_box_console");
  if (!console) {
    return lua_state.push_nil();
  }
  if (!lua_state.isfunction(-1) || !lua_state.isfunction(-2) ||
      !lua_state.isstring(-3) || !lua_state.isstring(-4)) {
    console->box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]The argument of add_switch invalid");
    return lua_state.push_nil();
  }
  std::string switch_name = lua_state.get<const char *>(-4);
  const auto *switch_tips = lua_state.get<const char *>(-3);
  auto refresh_index = console->new_index();
  auto change_index = console->new_index();
  if (!lua_state.add_lua_reg_function(console->lua_reg_key_, change_index)) {
    console->box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]Add switch failed, switch name:" + switch_name);
    return lua_state.push_nil();
  }
  lua_state.push_value(-2);
  if (!lua_state.add_lua_reg_function(console->lua_reg_key_, refresh_index)) {
    console->box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]Add switch failed, switch name:" + switch_name);
    lua_state.pop(1);
    return lua_state.push_nil();
  }
  lua_state.pop(1);
  auto index =
      console->add_switch_callback({switch_name, refresh_index, change_index});
  auto switch_plugin = kratos::console::ConsoleSwitchPlugin{
      std::bind(&LuaConsole::switch_refresh_cb, console, std::placeholders::_1,
                std::placeholders::_2),
      std::bind(&LuaConsole::switch_change_cb, console, std::placeholders::_1,
                std::placeholders::_2, std::placeholders::_3),
      (std::uint64_t)index};
  console->box_->get_console()->add_switch(switch_name, switch_plugin);
  return lua_state.push(true);
}

int kratos::lua::LuaConsole::lua_add_selection(lua_State *l) {
  LuaUtil::LuaState lua_state(l);
  auto *console = get_class<LuaConsole>(l, "_box_console");
  if (!console) {
    return lua_state.push_nil();
  }
  if (!lua_state.isfunction(-1) || !lua_state.isfunction(-2) ||
      !lua_state.isstring(-3) || !lua_state.isstring(-4)) {
    console->box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]The argument of add_selection invalid");
    return lua_state.push_nil();
  }
  std::string selection_name = lua_state.get<const char *>(-4);
  const auto *selection_tips = lua_state.get<const char *>(-3);
  auto refresh_index = console->new_index();
  auto change_index = console->new_index();
  if (!lua_state.add_lua_reg_function(console->lua_reg_key_, change_index)) {
    console->box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]Add selection failed, selection name:" + selection_name);
    return lua_state.push_nil();
  }
  lua_state.push_value(-2);
  if (!lua_state.add_lua_reg_function(console->lua_reg_key_, refresh_index)) {
    console->box_->write_log_line(
        klogger::Logger::FAILURE,
        "[lua][console]Add selection failed, selection name:" + selection_name);
    lua_state.pop(1);
    return lua_state.push_nil();
  }
  lua_state.pop(1);
  auto index = console->add_selection_callback(
      {selection_name, refresh_index, change_index});
  auto selection_plugin = kratos::console::ConsoleSelectionPlugin{
      std::bind(&LuaConsole::selection_refresh_cb, console,
                std::placeholders::_1, std::placeholders::_2),
      std::bind(&LuaConsole::selection_change_cb, console,
                std::placeholders::_1, std::placeholders::_2,
                std::placeholders::_3),
      (std::uint64_t)index};
  console->box_->get_console()->add_selection(selection_name, selection_plugin);
  return lua_state.push(true);
}
